Skip to content

Make queue-executed ASE (UMA/fairchem) jobs runnable on a cluster - #985

Open
alongd wants to merge 3 commits into
mainfrom
queue-executed-ase-adapter
Open

alongd wants to merge 3 commits into
mainfrom
queue-executed-ase-adapter

Conversation

@alongd

@alongd alongd commented Aug 15, 2026

Copy link
Copy Markdown
Member

What

Make queue-executed ASEAdapter jobs actually run on a remote cluster. The ASE adapter had only ever been exercised incore; its queue path was latent and broke on the first real submission. This lets ASE-backed calculators (e.g. UMA / fairchem MLIPs) run hindered-rotor and other scans as ordinary PBS/Slurm jobs.

Why

Running a real ARC job that delegates its hindered-rotor scans to an ASE calculator on a cluster queue crashed the moment the first scan job was dispatched:

arc.exceptions.InputError: Cannot upload a non-existing file.
Check why file in path .../directed_scan_a2781/submit.sh is missing.

Two independent defects in the queue path, both masked because ASE had only ever run incore:

  1. Files were never written. JobAdapter.execute() calls upload_files() before execute_queue(), and _initialize_adapter() calls set_files() while the job is constructed. The Gaussian/Orca/xTB adapters write submit.sh / input inside set_files(); ASEAdapter.set_files() only appended their names to files_to_upload and never wrote them, so the calcs dir was empty when ssh.upload_file() ran its os.path.isfile pre-flight.
  2. Submission was dead code. ASEAdapter.execute_queue() guarded on self.server_adapter, an attribute nothing in ARC sets (hasattrFalse), so even with the files present the job would never submit — the write fix alone just moves the crash one line down to an AttributeError.

Changes (confined to the ASE adapter)

  • set_files() now writes submit.sh and input.yml for non-incore jobs, mirroring the other adapters. Incore is unchanged — it still writes its input in execute_incore().
  • execute_queue() now delegates to the shared legacy_queue_execution() that every other adapter uses (which also records job_status / job_id), replacing the bespoke never-reached block.
  • write_submit_script()'s non-queue branch falls back to local_path when remote_path is None (a server-less job), fixing a latent crash surfaced by writing at construction time.
  • Adds test_set_files_writes_the_files_of_a_queue_job (red against the pre-fix adapter).

Verification

  • New/updated unit tests: pytest arc/job/adapters/ase_test.py -q -n010 passed (the file requires serial -n0, a pre-existing xdist setUpClass race unrelated to this change).
  • Incore path unregressed: full execute()execute_incore()ase_script.pyparse_results() reproduces the prior geometry/energy to 5 decimals.
  • Live cluster run: an ARC job (DFT opt/freq/sp on the queue, hindered-rotor scans via a UMA/ASE calculator) now runs end-to-end — the DFT stage completes and the rotor jobs are written with their submit.sh + input.yml and submitted to the queue, where the pre-fix run crashed.

The diff is limited to arc/job/adapters/ase_adapter.py, arc/job/adapters/ase_test.py, and a 1-indexed→0-indexed constraint fix in arc/job/adapters/scripts/ase_script.py (ASE FixInternals is 0-indexed; ARC constraints are 1-indexed). No other adapter's submit path is touched.

Comment thread arc/job/adapters/ase_adapter.py Fixed
@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.27%. Comparing base (575d216) to head (5647e0f).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #985   +/-   ##
=======================================
  Coverage   66.26%   66.27%           
=======================================
  Files         122      122           
  Lines       41824    41874   +50     
  Branches    10751    10754    +3     
=======================================
+ Hits        27716    27752   +36     
- Misses      11062    11072   +10     
- Partials     3046     3050    +4     
Flag Coverage Δ
functionaltests 66.27% <ø> (+<0.01%) ⬆️
unittests 66.27% <ø> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the previously-latent “queue execution” path for ASEAdapter so ASE-backed calculators (e.g., UMA/fairchem MLIPs) can run as real cluster jobs, including rotor/directed-scan workloads that ARC dispatches via the scheduler.

Changes:

  • Write submit + input.yml during adapter construction for non-incore ASE jobs so upload_files() succeeds.
  • Replace ASE’s bespoke queue submission logic with the shared legacy_queue_execution() flow.
  • Correct ASE internal constraint indexing by translating ARC’s 1-indexed constraints to ASE FixInternals’ 0-indexed expectations.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
arc/job/adapters/ase_adapter.py Writes queue job files in set_files(), adds directed-scan constraints translation, and delegates queue submission to legacy_queue_execution().
arc/job/adapters/ase_test.py Adds unit coverage asserting queue-job construction writes/uploadables exist on disk (and that incore does not).
arc/job/adapters/scripts/ase_script.py Converts constraint atom indices from ARC’s 1-indexing to ASE’s 0-indexing before applying FixInternals.
Suppressed comments (1)

arc/job/adapters/ase_adapter.py:415

  • This method always writes the submit script to submit.sh. On Slurm, ARC submits submit.sl (see settings['submit_filenames']), so Slurm jobs will still error unless the script is written under the Slurm filename.
        with open(os.path.join(self.local_path, 'submit.sh'), 'w') as f:
            f.write(content)

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread arc/job/adapters/ase_adapter.py Outdated
@alongd

alongd commented Aug 15, 2026

Copy link
Copy Markdown
Member Author

Added a third commit (dc23057): fix a stale directed-scan parser call site — check_directed_scan_job called parser.parse_e_elect(path=...) but the refactored API is log_file_path= (as every other call site uses). Without it, ARC crashes assembling V(φ) the moment a directed-scan job's energy is parsed. Verified by parsing a real UMA rotor output (−500058.16 kJ/mol). This is needed for ASE/UMA rotor scans to run end-to-end.

Comment thread arc/job/adapters/ase_adapter.py Outdated
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from 65e4d98 to 76b7b19 Compare August 21, 2026 20:08
@alongd
alongd requested a lite review from Copilot August 21, 2026 22:54
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from 76b7b19 to 3489549 Compare August 21, 2026 22:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Suppressed comments (6)

arc/job/adapters/ase_adapter.py:284

  • Slurm jobs will still fail at submission: write_submit_script() writes submit.sh, and this entry uploads/registers that same name, but legacy_queue_execution() invokes the shared submit_filenames['Slurm'] (submit.sl; see arc/settings/settings.py:173-176). Thus sbatch submit.sl cannot find the file. Use the shared scheduler-specific filename consistently when writing and registering the script.
            self.files_to_upload.append(self.get_file_property_dictionary(file_name='submit.sh'))

arc/job/adapters/ase_adapter.py:354

  • max_job_time is not propagated into these templates: format_kwargs has no t_max, and the PBS/Slurm templates have no walltime directive. Unlike the shared JobAdapter.write_submit_script() path (arc/job/adapter.py:287-315), an ASE queue job will use the scheduler default limit and can be killed before ARC's requested time. Add the scheduler-specific walltime directive and pass the formatted value.
        format_kwargs = {'name': self.job_server_name, 'cpus': self.cpu_cores, 'memory': memory,
                         'remote_path': self.remote_path, 'env_setup': config['env_setup'], 'command': command}

arc/job/adapters/ase_adapter.py:376

  • queue_job is false for every non-incore scheduler other than PBS/Slurm, so OGE/HTCondor (and a no-queue local server) take the bare-script branch but are still submitted through legacy_queue_execution(). HTCondor then looks for submit.sub, while only submit.sh was written; other schedulers also lack their directives. Raise for unsupported schedulers or add templates instead of silently submitting an invalid script.
        queue_job = self.execution_type != 'incore' and cluster_soft in ('pbs', 'slurm')

arc/job/adapters/scripts/ase_script.py:127

  • The new index conversion is not covered by the ASE tests: there is no test for apply_constraints() or for determine_constraints() generating a directed-scan constraint. A regression here would silently apply a constraint to the wrong atoms, so add a test that writes a 1-indexed four-atom dihedral constraint and verifies the FixInternals indices are 0-indexed.
        indices = [index - 1 for index in constraint[0]]

arc/settings/submit.py:128

  • The PR description says the diff is confined to three ASE adapter files, but this change also adds arc/settings/submit.py and modifies arc/imports.py. Please update the description or explain these additional shared submission/configuration changes, since they materially expand the review scope.
# Submission scripts for queue-executed ASE (e.g. UMA/fairchem MLIP) jobs, keyed by cluster

arc/settings/submit.py:146

  • ARC invokes sbatch after changing into the submission directory, but this script changes into remote_path as if the batch job starts at the remote home. When the server has no path, remote_path is relative, so this can resolve to a nested runs/.../runs/... directory; for a local queue it also points away from local_path. Use the scheduler's submission directory for the job directory instead.
cd "{remote_path}"
JOB_DIR="$(pwd)"  # absolute path (the path above is relative to the remote home, where the job starts)

Comment thread arc/job/adapters/ase_adapter.py
Comment thread arc/job/adapters/ase_adapter.py Outdated
Comment thread arc/settings/submit.py
@alongd
alongd force-pushed the queue-executed-ase-adapter branch 2 times, most recently from cb346da to 6e97a32 Compare August 23, 2026 05:39
alongd added a commit to alongd/ARC that referenced this pull request Aug 23, 2026
alongd added a commit to alongd/ARC that referenced this pull request Aug 23, 2026
Comment thread arc/job/adapters/ase_adapter.py
alongd added a commit to alongd/ARC that referenced this pull request Aug 25, 2026
alongd added a commit to alongd/ARC that referenced this pull request Aug 25, 2026
alongd added a commit to alongd/ARC that referenced this pull request Aug 26, 2026
@alongd
alongd force-pushed the queue-executed-ase-adapter branch 3 times, most recently from 35e6091 to 6c8424e Compare August 30, 2026 05:23
Comment thread arc/imports.py Fixed
ASEAdapter.write_submit_script() emitted a bare two-line bash script: no
scheduler directives, no queue, no environment activation, and the ARC host's
conda python path, which does not exist on the server. It now composes a PBS
or Slurm script from args['block'] (queue, env_setup, gpu_resource, python),
pins the thread pools to the granted core count so torch cannot oversubscribe
a shared node, requests the job's walltime so a long scan is not killed at the
queue default, and stamps initial_time/final_time so ARC can report a run
time. The script body lives in a server-independent ase_submit template in
arc/settings/submit.py (keyed by cluster software, mirroring pipe_submit and
wired through arc.imports with the same local-override hook), so it can be
customized per cluster like every other submit script; the adapter only fills
in placeholders. The script is written under the scheduler's submit filename
(submit.sl for Slurm), which is the name submit_job() invokes, and cd's into
the submission directory (the local path for a 'local' server). The resolved
queue is recorded in attempted_queues, as JobAdapter does, so a failed
submission moves on to the next queue instead of retrying the same one.
Incore jobs keep the bare script.

A queue-executed ASE job also never wrote its submit script or input.yml.
set_files() only listed them for upload, but JobAdapter.execute() uploads
before it calls execute_queue(), so the upload died with "InputError: Cannot
upload a non-existing file". Write them in set_files(), where Gaussian, Orca
and xTB write theirs; the incore path still writes its input in
execute_incore(). execute_queue() then never submitted anything either: it
guarded on self.server_adapter, an attribute nothing sets. Use
legacy_queue_execution(), as every other adapter does, which also records the
job id and status.

Directed scans were also not constrained: Scheduler.run_job() always passes
constraints=None and hands the adapter torsions + dihedrals instead, so every
point of a brute_force_opt scan optimized freely and relaxed to the same
minimum. ASEAdapter.determine_constraints() derives the constraint, and
apply_constraints() converts ARC's 1-indexed atom indices to ASE's 0-indexed
FixInternals. The shared Scheduler/Gaussian side of that defect is left alone
here; a Gaussian directed_scan job needs its own fix.
@alongd
alongd force-pushed the queue-executed-ase-adapter branch from d7d3a1f to eac1707 Compare September 13, 2026 19:55
@alongd

alongd commented Sep 13, 2026

Copy link
Copy Markdown
Member Author

Rebased onto current main (was BEHIND) and re-verified. Everything raised in review is addressed at the tip; posting the mapping so a re-review does not have to reconstruct it.

@calvinp0 — both of your points:

  • Case corruption. You were right that the lowercasing hit all four block keys, not just env_setup. Fixed at source in arc/level.py: CASE_SENSITIVE_BLOCK_ARGS = ('python', 'env_setup', 'queue', 'gpu_resource'), and Level.lower() now preserves those four while lowercasing everything else as before. Your example survives verbatim — CUDA/12.1, UMA_env, GPU_Long and the full interpreter path all keep their case. Covered by test_lower_preserves_case_sensitive_block_args.
  • Docs. Added "Running ASE / UMA jobs on a cluster" to docs/source/advanced.rst (§ at line 388), built around the block example you wrote.

Copilot's four:

  • Slurm would upload submit.sh while ARC submits submit.sldetermine_submit_filename() keys off submit_filenames[cluster_soft] for queue jobs and keeps the plain name for incore.
  • Resolved queue never added to attempted_queues, so trsh_job_queue() can retry the same queue → recorded in write_submit_script() before returning, as JobAdapter does.
  • Template always formatted with remote_path, so a local queue job cds into the wrong directorypwd is local_path when the server is None or local.
  • PBS template omitted walltime#PBS -l walltime={t_max} is in the template, with the value formatted through t_max_format per cluster software.

CodeQL's two: implicit string concatenation in the submit list, and the bare except: pass in arc/imports.py — the ase_submit overlay was the only one of the four swallowing ImportError silently and now calls _report_unusable_overlay() like its siblings.

Tests: arc/job/adapters/ase_test.py 18 passed, arc/level_test.py 13 passed. Three level_test failures reproduce identically on unmodified main in this environment and are unrelated to this branch.

One note for whoever reviews: a separate defect in this same area is up as #1054 (rotor_top() inherits ASE's 0.3 Å neighbour-list skin, fuses 1-3 pairs into a spurious ring, and rejects every rotor of a branched molecule). It is deliberately not folded in here — different file, different cause, and this PR is large enough. Both are needed before an ASE rotor scan actually completes on a cluster; verified end to end on zeus today with the two together.

@alongd
alongd requested a review from calvinp0 September 13, 2026 19:59
alongd added a commit to alongd/ARC that referenced this pull request Sep 21, 2026
Comment thread arc/job/adapters/ase_adapter.py Outdated
config = self.determine_submit_config()
cluster_soft = servers.get(self.server, dict()).get('cluster_soft', '').lower() \
if self.server is not None else ''
queue_job = self.execution_type != 'incore' and cluster_soft in ('pbs', 'slurm')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we also deal with htcondor and oge?

Comment thread arc/job/adapters/ase_test.py Outdated

def test_set_files_does_not_write_for_an_incore_job(self):
"""Test that an incore job writes no submit script (it writes its input when it executes)"""
self.assertFalse(os.path.isfile(os.path.join(self.job_1.local_path, 'submit.sh')))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the assertion can't fail; setUpClass reassigns local_path after construction, so it checks a directory set_files() never wrote to.

"""
cluster_soft = servers.get(self.server, dict()).get('cluster_soft', '') if self.server is not None else ''
if self.execution_type != 'incore' and cluster_soft.lower() in ('pbs', 'slurm'):
return submit_filenames[cluster_soft]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do a lower case guard but then the lookup isn't lowercase

@calvinp0 calvinp0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just 3 minor comments

`determine_submit_filename()` guarded on `cluster_soft.lower() in ('pbs', 'slurm')`
but then looked the name up as `submit_filenames[cluster_soft]`, verbatim. Since
that dict is keyed by the exact settings spelling ('PBS', 'Slurm'), a server whose
`cluster_soft` is spelled any other way passed the guard and raised KeyError while
the job was still being constructed.

The lookup is the correct half: `local.submit_job()`/`ssh.submit_job()` resolve the
file to submit the same verbatim way, so lowercasing here would name a file the
submission never invokes. Tightened the guard to the lookup's own precondition via
a `QUEUE_CLUSTER_SOFT` constant, used by `write_submit_script()` too so both agree,
and kept the lower-cased key only where it belongs - `ase_submit`, ARC's own dict.

A queue job on a scheduler with no `ase_submit` template (HTCondor, OGE) now says
so rather than silently writing a bare script with no scheduler directives.
`test_set_files_does_not_write_for_an_incore_job` could not fail: `setUpClass`
repoints `local_path` at a flat scratch directory after construction, so the
assertion checked a directory `set_files()` had never written to. It now asserts
against the path the adapter resolved for itself, which `set_files()` actually
used.

Added regression tests for the rest of the queue path, each of which fails against
the defect it covers: the submit script is written under the filename
`submit_job()` invokes (Slurm's `submit.sl`, not `submit.sh`); an unknown
`cluster_soft` spelling falls back instead of raising; a job on the `local` server
cd's into its local path rather than a remote path that is not there; the resolved
queue reaches `attempted_queues`, without which `trsh_job_queue()` retries the same
queue forever; and `max_job_time` reaches the script instead of the queue default.
@alongd

alongd commented Sep 22, 2026

Copy link
Copy Markdown
Member Author

Worked every open finding on this PR. Pushed two commits (fast-forward, no rebase, no force-push): fd8e795c0 and 5647e0f8e.

Short version: one of the nine was still a live defect (finding 1). Five had already been fixed on the branch but carried no test, so they now have one. Two were answered. One was already resolved.


1. ase_adapter.py:377 — "we do a lower case guard but then the lookup isn't lowercase" (@calvinp0)

Real, and it raised. Reproduced against the branch tip: a server whose cluster_soft is anything but the exact settings spelling passes cluster_soft.lower() in ('pbs', 'slurm') and then dies inside the constructor —

cluster_soft='PBS'      adapter->'submit.sh'   submit_job()->'submit.sh'
cluster_soft='Slurm'    adapter->'submit.sl'   submit_job()->'submit.sl'
cluster_soft='slurm'    RAISED KeyError: 'slurm'
cluster_soft='pbs'      RAISED KeyError: 'pbs'
cluster_soft='SLURM'    RAISED KeyError: 'SLURM'

Which way I resolved it, and why: the lookup is the correct half; the guard was wrong. Lowercasing the lookup would break every server, because submit_filenames has no lowercase keys at all — it is {'OGE', 'Slurm', 'PBS', 'HTCondor'}. More importantly, this adapter is not free to pick its own convention: the filename it writes has to be the one the submission actually invokes, and local.submit_job() (arc/job/local.py:246) and ssh.submit_job() (arc/job/ssh.py:475) both resolve that as submit_filenames[cluster_soft], verbatim. A case-insensitive resolution here would cheerfully name submit.sl on a server where submit_command['slurm'] then KeyErrors anyway — a file named for a submission that cannot happen.

So the guard is now the lookup's own precondition, hoisted into a named constant so the two cannot drift apart again:

# Cluster schedulers this adapter carries an ``ase_submit`` template for, spelled exactly as the
# keys of ``submit_filenames``/``submit_command`` in arc/settings/settings.py. ...
QUEUE_CLUSTER_SOFT = ('PBS', 'Slurm')

write_submit_script() uses the same constant, so the filename decision and the template decision can no longer disagree. The lower-cased key survives in exactly one place — ase_submit, which is ARC's own dict and is keyed in lower case, like pipe_submit. That split is now stated in a comment at the call site rather than being something you have to infer.

Worth noting this exact guard/lookup mismatch also exists upstream of the adapter — ssh.py:442 validates cluster_soft.lower() not in [...] and ssh.py:475 looks up verbatim — so a mis-cased cluster_soft was never going to run. It now fails where it is diagnosable instead of mid-construction. Not fixing that one here; it is not this branch's.

Covered by test_submit_filename_is_the_one_the_submission_path_invokes and test_submit_filename_guard_admits_only_what_the_lookup_can_serve.

2. ase_test.py:162 — "the assertion can't fail" (@calvinp0)

Real. setUpClass repoints job_1.local_path at a flat scratch directory after construction, so assertFalse(os.path.isfile(join(job_1.local_path, 'submit.sh'))) checked a directory set_files() had never written to.

I watched it fail. One-line deliberate break in set_files()if self.execution_type != 'incore':if True:, so an incore job does write a submit script — then the flagged assertion in isolation:

constructed local_path (what set_files() wrote to): .../test_ASEAdapter/test_1/calcs/Species/H2O/sp_a1
reassigned  local_path (what the assertion checks): .../test_ASEAdapter/test_1
submit.sh actually on disk in constructed path     : True

ORIGINAL assertion  assertFalse(isfile(join(job_1.local_path,'submit.sh')))  -> isfile=False -> PASSES (cannot fail)
REPAIRED assertion  assertEqual(submit.* in constructed path, [])           -> ['submit.sh'] -> FAILS

and through pytest, the repaired test against the same broken code:

>       self.assertEqual([entry for entry in os.listdir(self.job_1_constructed_local_path)
                          if entry.startswith('submit.')], list())
E       AssertionError: Lists differ: ['submit.sh'] != []

The break is reverted; it is not in either commit. setUpClass now keeps the constructed paths (job_1_constructed_local_path), and the assertion checks for any submit.*, so it does not go blind again if the scheduler-specific name changes.

3. ase_adapter.py:429 — "should we also deal with htcondor and oge?" (@calvinp0)

Not a defect — and my answer is "not on this branch", with reasons.

What the adapter supports today: PBS and Slurm, the two keys in ase_submit. Anything else reaching get_queue_submit_script() raises NotImplementedError naming the available templates.

What ARC's other adapters do: nothing scheduler-generic at all. Gaussian, Orca, QChem, Molpro, TeraChem, CFour, xTB and the TS adapters all read submit_scripts[server][software] — a per-server, per-software dict a user fills in for their own machine. By that standard ase_submit already covers more schedulers than any ESS adapter in ARC. The only real precedent for a server-independent scheduler-keyed dict is pipe_submit (slurm, pbs, sge, htcondor, local), and it is instructive about why the remaining two are not mechanical:

  • HTCondor is not a shell script. submit_filenames['HTCondor'] is submit.sub, and pipe_submit['htcondor'] is a condor submit description file (executable = …, queue N), not bash. Everything the ASE template does lives in shell: cd "{pwd}", JOB_DIR="$(pwd)", the OMP_NUM_THREADS/MKL/OPENBLAS pinning, {env_setup} (module load CUDA/12.1; conda activate uma_env), and touch initial_time / touch final_time — which JobAdapter.determine_run_time() reads back off the server (arc/job/adapter.py:609-627) to time the job. Under HTCondor all of that has to move into a separate wrapper that executable = points at. That means a second file in set_files(), a second name out of determine_submit_filename(), and request_gpus in place of the gpu_select/--gres mapping. That is an adapter design change, not a template.
  • OGE/SGE is a naming question I should not settle unilaterally. submit_filenames/submit_command/check_status_command spell it OGE; pipe_submit spells it sge and pipe_run.py:261 carries an explicit bridge, template_key = 'sge' if self.cluster_software == 'oge' else self.cluster_software. submit_filenames has no SGE key at all, so an SGE server already KeyErrors in submit_job() before ASE is reached. Picking a spelling for ase_submit is a settings-wide decision.
  • Neither can be verified from here. There is no HTCondor or OGE server in settings.py or in the fixtures, and I am not submitting to a cluster on this branch. A template that has never run under its scheduler is worse than an explicit refusal.

What I did do, since a silent wrong answer was the actual hazard: a queue job on a scheduler with no ase_submit template used to write a bare directive-less script and say nothing. It now warns, and determine_submit_filename() and write_submit_script() agree on that path. Test: test_write_submit_script_warns_on_a_scheduler_with_no_template.

If you want HTCondor, the work is the two-file wrapper design above; say the word and it is a separate PR with a request_gpus mapping and a submit.sub + wrapper pair.

4. ase_adapter.py:284 / :414 — "Slurm jobs never upload their submit script"

Was real; already fixed on this branch, and now has a test. Verified rather than assumed — on the current tip a Slurm queue job gives

files_to_upload: ['submit.sl', 'input.yml', 'ase_script.py']
submit_filenames['Slurm'] = submit.sl -> present: True
on disk: ['input.yml', 'submit.sl']

determine_submit_filename() is what fixed it. It had no test, which is how finding 1 hid inside it; test_submit_filename_is_the_one_the_submission_path_invokes now pins the filename, its presence on disk, and its presence in files_to_upload, for PBS and Slurm.

5. ase_adapter.py:354 — "local queue submissions cd into the wrong directory"

Was real; already fixed (pwd = self.local_path if … 'local' else self.remote_path), now has a test. Verified on a local server with a queue execution type:

local_path : .../calcs/Species/H2O/opt_a1
remote_path: runs/ARC_Projects/f5/H2O/opt_a1
script cd  : ['cd ".../calcs/Species/H2O/opt_a1"']

test_write_submit_script_enters_the_local_path_of_a_local_server asserts the cd target and that the remote path appears nowhere in the script.

6. ase_adapter.py:361 — "the resolved queue is never recorded in attempted_queues"

Was real; already fixed, now has a test. Both routes verified: the server-default queue and args['block']['queue'] each land in attempted_queues and in the #PBS -q directive, so trsh_job_queue() can move a failed submission on. test_write_submit_script_records_the_resolved_queue.

7. submit.py:165 — "the PBS template omits walltime entirely"

Was real; already fixed, now has a test. #PBS -l walltime={t_max} is in the template and t_max is formatted per t_max_format. test_write_submit_script_requests_the_job_walltime checks both schedulers, including that Slurm gets its days format:

max_job_time=12.0, PBS   -> #PBS -l walltime=12:00:00
max_job_time=48.0, Slurm -> #SBATCH -t 2-0:00:00

8. CodeQL ase_adapter.py:366 — implicit string concatenation in a list

Already resolved, twice over. Alert 1953 was dismissed as won't fix by @alongd on 2026-08-15, and the construct no longer exists — moving the template into settings/submit.py removed the list it lived in. Confirmed by walking the module's AST for adjacent string constants inside any list/tuple/set literal: implicit concat inside list/tuple/set literals: NONE. Nothing to change.

9. ase_adapter.py:357 — "best to put it in settings/submit.py like all other submit scripts" (@alongd)

Confirmed done from the code, not from the thread. arc/settings/submit.py:134 defines ase_submit = {'slurm': …, 'pbs': …} as server-independent templates; arc/imports.py:15 imports it and lines 138-151 give it the same ~/.arc/submit.py overlay machinery as incore_commands/pipe_submit/submit_scripts, reporting an unusable overlay through _report_unusable_overlay rather than a bare pass (which was the separate CodeQL finding on imports.py:140). get_queue_submit_script() is down to computing placeholder values and .format()-ing the template. The two companion halves are there too: CASE_SENSITIVE_BLOCK_ARGS at arc/level.py:20, and the "Running ASE / UMA jobs on a cluster" section at docs/source/advanced.rst:388.


Tests

conda activate arc_env, pytest -n0, counts before → after:

module before after
arc/job/adapters/ase_test.py 21 27 (+7 subtests)
arc/level_test.py 17 17
arc/job/adapter_test.py 42 42
arc/imports_test.py 11 11
total 91 97

Wider sweep over arc/job/ + arc/settings/ + arc/level_test.py + arc/imports_test.py: 1515 passed, 5 skipped, 2 failed. Both failures are pyscf_test.py::test_run_freq_*, from PySCF not being installed in this environment; I re-ran them on the unmodified tip and they fail identically there, so they are not from this work.

One thing left for you

The branch is BEHIND main (mergeStateStatus: BEHIND) and needs a rebase before it can merge. I deliberately did not do it — rebasing and force-pushing a branch that is under review is yours to call, not mine.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants